//
//  HKKeychain.swift
//  
//
//  Created by xp on 2023/3/29.
//

import Foundation

//参考: https://www.advancedswift.com/secure-private-data-keychain-swift/

open class HKKeychain {
    
    open class func save(data: Data, account: String, forService: String? = nil) throws {
        let service = forService ?? HKBundleId ?? account
        let cfDic: [CFString: Any] = [
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecClass: kSecClassGenericPassword,
            kSecValueData: data as CFData
        ]
        let result = SecItemAdd(cfDic as CFDictionary, nil)
        if result == errSecDuplicateItem {
            throw HKKeychainError.duplicateItem
        }
        if result != errSecSuccess {
            throw HKKeychainError.other(result)
        }
    }
    
    open class func update(data: Data, account: String, forService: String? = nil) throws {
        let service = forService ?? HKBundleId ?? account
        let cfDic: [CFString: Any] = [
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecClass: kSecClassGenericPassword
        ]
        
        let attr: [CFString: Any] = [
            kSecValueData: data as CFData
        ]
        
        let re = SecItemUpdate(cfDic as CFDictionary, attr as CFDictionary)
        
        if re == errSecItemNotFound {
            throw HKKeychainError.itemNotFound
        }
        if re != errSecSuccess {
            throw HKKeychainError.other(re)
        }
    }
    
    open class func readData(account: String, forService: String? = nil) throws -> Data {
        let service = forService ?? HKBundleId ?? account
        let cfDic: [CFString: Any] = [
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecClass: kSecClassGenericPassword,
            kSecMatchLimit: kSecMatchLimitOne,
            kSecReturnData: kCFBooleanTrue as Any
        ]
        var item: AnyObject?
        let re = SecItemCopyMatching(cfDic as CFDictionary, &item)
        
        guard re != errSecItemNotFound else {
            throw HKKeychainError.itemNotFound
        }
        guard re == errSecSuccess else {
            throw HKKeychainError.other(re)
        }
        guard let data = item as? Data else {
            throw HKKeychainError.invalidItemFormat
        }
        return data
    }
    
    open class func delete(account: String, forService: String? = nil) throws {
        let service = forService ?? HKBundleId ?? account
        let cfDic: [CFString: Any] = [
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecClass: kSecClassGenericPassword
        ]
        let re = SecItemDelete(cfDic as CFDictionary)
        if re != errSecSuccess {
            throw HKKeychainError.other(re)
        }
    }
}

extension HKKeychain {
    public enum HKKeychainError: Error {
        case duplicateItem
        case invalidItemFormat
        case itemNotFound
        case other(OSStatus)
    }
}
